package org.proxywars.engine.scene.shape;

import java.nio.FloatBuffer;

import com.jme.image.Texture;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.TexCoords;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;

public class TexturedRectangle3d extends Rectangle3d {
    
    /* Constant Variables */
    private static final long serialVersionUID = 1L;
    
    /** Texture mode constant: used to determine how texture is applied - stretched.  */ 
    public static final int TEXTURE_STRETCHED = 0;
    /** Texture mode constant: used to determine how texture is applied 
     * - tiled both vertically and horizontally. */
    public static final int TEXTURE_TILED = 1;
    /** Texture mode constant: used to determine how texture is applied - tiled horizontally.  */ 
    public static final int TEXTURE_TILED_X = 2;
    /** Texture mode constant: used to determine how texture is applied - tiled vertically.  */ 
    public static final int TEXTURE_TILED_Y = 3;
    
    /* Protected Variables */
    /** TextureState used to apply the texture if a texture is set. */
    protected TextureState textureState;
    
    /** Determines if the rectangle is textured. */
    protected boolean isTextured = false;
    
    protected int textureX;
    protected int textureY;
    protected int texturePixelWidth;
    protected int texturePixelHeight;
    
    
    /* Private Variables */
    /** Texture applied to the rectangle */
    private Texture texture;
    /** Width of the applied texture */
    private int textureWidth;
    /** Height of the applied texture */
    private int textureHeight;
    
    private int textureMode = TEXTURE_STRETCHED;    // Determines how to stretch or tile.
    

    
    
    public TexturedRectangle3d() {
    }

    public TexturedRectangle3d(String name) {
        super(name);
    }

    public TexturedRectangle3d(String name, int width, int height) {
        super(name, width, height);
    }

    public TexturedRectangle3d(String name, int x, int y, int width, int height) {
        super(name, x, y, width, height);
    }
    
    /**
     * Convenience function so that all the constructors just call one function.
     * Makes it easier to change/update or for children to initialize all the values as well.
     */
    @Override
    protected void initialize(String name, int x, int y, int width, int height) {
        /* Setup TextureState */
        textureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        textureState.setEnabled(false);
        setRenderState(textureState);
        
        /* initialize */
        super.initialize(name, x, y, width, height);
    }
    
    
    /* Get Methods */
    /**
     * Returns the texture applied to the rectangle.
     * @return the texture applied to the rectangle or <code>null</code> if no texture is applied.
     */
    public Texture getTexture() {
        return texture;
    }
    
    /**
     * Get the transparency of the rectangle.
     * 
     * @return the transparency of the rectangle.
     */
    public float getTransparency() {
        ColorRGBA defaultColor = getDefaultColor();
        return defaultColor.a;
    }
    
    /* Private Get Methods */
    /**
     * Convenient method to return the U (x) texture coordinate from image coordinate.
     *
     * @param xPixel x image coordinate
     * @return the U texture coordinate
     */
    private float getUForPixel(final int xPixel) {
        return (float) xPixel / (float)textureWidth;
    }

    /**
     * Convenient method to return the V (y) texture coordinate from image coordinate.
     *
     * @param yPixel y image coordinate
     * @return the V texture coordinate
     */
    private float getVForPixel(final int yPixel) {
        return 1f - (float) yPixel / (float)textureHeight;
    }


    /* Set Methods */    
    /**
     * Set the texture for the rectangle.
     * By default it will load the entire image from the top left corner. 
     * 
     * @param fileName      name of image to set as texture.
     */
    public void setTexture(String fileName) {
        setTexture(fileName, 0, 0, 0, 0);
    }

    /**
     * Sets the texture for the rectangle.
     * The pixel width/height are the for the actual image data, 
     * some textures may have excess transparent data to enforce a power of 2 texture.
     * <p>
     * <b>Note:</b> Tiled textures ignore the x, y, width, and height and simply tile the entire texture!
     * Make sure that your tiled textures use the entire image file, unless you want transparent spots.
     * 
     * @param fileName                      name of image to set as texture.
     * @param textureX                      X point of top left pixel in file.
     * @param textureY                      Y point of top left pixel in file.
     * @param texturePixelWidth         width of the image within the texture.
     * @param texturePixelHeight        height of the image within the texture.
     */
    public void setTexture(String fileName, int textureX, int textureY, int texturePixelWidth, int texturePixelHeight) {
        //TODO: test how this works with jars and such.
        texture = TextureManager.loadTexture(
                getClass().getResource(fileName),
                Texture.MinificationFilter.Trilinear, // of no use for the quad
                Texture.MagnificationFilter.Bilinear, // of no use for the quad
                1.0f,
                true);
        
        /* if no value was passed in, set to the same size as the image */
        texturePixelWidth = (texturePixelWidth == 0 ? texture.getImage().getWidth() : texturePixelWidth);
        texturePixelHeight = (texturePixelHeight == 0 ? texture.getImage().getHeight() : texturePixelHeight);
        
        setTexture(texture, textureX, textureY, texturePixelWidth, texturePixelHeight);
    }
    
    /**
     * Sets the texture for the rectangle.
     * The pixel width/height are the for the actual image data, 
     * some textures may have excess transparent data to enforce a power of 2 texture.
     * <p>
     * <b>Note:</b> Tiled textures ignore the x, y, width, and height and simply tile the entire texture!
     * Make sure that your tiled textures use the entire image file, unless you want transparent spots.
     * 
     * @param texture                        the texture to apply to the rectangle.
     * @param textureX                      X point of top left pixel in file.
     * @param textureY                      Y point of top left pixel in file.
     * @param texturePixelWidth         width of the image within the texture.
     * @param texturePixelHeight        height of the image within the texture.
     */
    public void setTexture(Texture texture, int textureX, int textureY, int texturePixelWidth, int texturePixelHeight) {
        this.textureX = textureX;
        this.textureY = textureY;
        this.texturePixelWidth = texturePixelWidth;
        this.texturePixelHeight = texturePixelHeight;
        
        isTextured = true;
        
        /* Load the texture */
        //textureState.deleteAll();
        
        /* Set and Activate the texture */
        textureState.setTexture(texture);
        textureState.setEnabled(true);

        /* set height and width of texture. */
        textureWidth = texture.getImage().getWidth();
        textureHeight = texture.getImage().getHeight();
        
        /* Apply the Texture */
        FloatBuffer texCoords = BufferUtils.createVector2Buffer(4);        
        if(textureMode == TEXTURE_STRETCHED) {
            /* Set the texture coords based on left corner of image within texture.
             * This is used when a texture has excess transparent area 
             * so that its size is a power of 2.
             */
            
            texCoords.clear();
            texCoords.put(getUForPixel(textureX)).put(getVForPixel(textureY));                                 // (x, y)
            texCoords.put(getUForPixel(textureX)).put(getVForPixel((textureY+texturePixelHeight))); // (x, y+height)
            texCoords.put(getUForPixel((textureX+texturePixelWidth))).put(getVForPixel((textureY+texturePixelHeight))); // (x+width, y+height)
            texCoords.put(getUForPixel((textureX+texturePixelWidth))).put(getVForPixel(textureY));  // (x+width, y)
            
        }
        else if(textureMode == TEXTURE_TILED) {
            /*
            texCoords.clear();     
            texCoords.put(0).put(texturePixelHeight);
            texCoords.put(0).put(0);
            texCoords.put(texturePixelWidth).put(0);
            texCoords.put(texturePixelWidth).put(texturePixelHeight);
            */
            
            /* TODO: make this use the x, y coords as above */          
            texCoords.clear();     
            texCoords.put(0).put(texturePixelHeight);
            texCoords.put(0).put(0);
            texCoords.put(texturePixelWidth).put(0);
            texCoords.put(texturePixelWidth).put(texturePixelHeight);
            
            texture.setWrap(Texture.WrapMode.Repeat);
            
        }
        else if(textureMode == TEXTURE_TILED_X) {
            
            /*
            texCoords.clear();
            texCoords.put(getUForPixel(0)).put(getVForPixel(0));
            texCoords.put(getUForPixel(0)).put(getVForPixel(texturePixelHeight));
            texCoords.put( texturePixelWidth ).put(getVForPixel(texturePixelHeight));
            texCoords.put( texturePixelWidth ).put(getVForPixel(0));
            */
            
            /* TODO: make this use the x, y coords as above */          
            texCoords.clear();
            texCoords.put(getUForPixel(0)).put(getVForPixel(0));
            texCoords.put(getUForPixel(0)).put(getVForPixel(texturePixelHeight));
            texCoords.put( texturePixelWidth ).put(getVForPixel(texturePixelHeight));
            texCoords.put( texturePixelWidth ).put(getVForPixel(0));
            
            texture.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);
        }
        else if(textureMode == TEXTURE_TILED_Y) {            
            /*
            texCoords.clear();
            texCoords.put(getUForPixel(0)).put(getVForPixel(0));
            texCoords.put(getUForPixel(0)).put( texturePixelHeight );
            texCoords.put(getUForPixel(texturePixelWidth)).put( texturePixelHeight );
            texCoords.put(getUForPixel(texturePixelWidth)).put(getVForPixel(0));
            */
            
            /* TODO: make this use the x, y coords as above */          
            texCoords.clear();
            texCoords.put(getUForPixel(0)).put(getVForPixel(0));
            texCoords.put(getUForPixel(0)).put( texturePixelHeight );
            texCoords.put(getUForPixel(texturePixelWidth)).put( texturePixelHeight );
            texCoords.put(getUForPixel(texturePixelWidth)).put(getVForPixel(0));
            
            texture.setWrap(Texture.WrapAxis.T, Texture.WrapMode.Repeat);         
        }
        
        setTextureCoords(new TexCoords(texCoords));     // assign texture coordinates
        setRenderState(textureState);                              // set the render state
    }
    
    /**
     * Sets the mode of applying the texture - scaled, tiled, tiled only on X-axis, 
     * or tiled only on Y-axis.
     * 
     * @param textureMode   Texture Mode Constants:
     *  <ul>
     *  <li><code>TEXTURE_STRETCHED</code></li>
     *  <li><code>TEXTURE_TILED</code></li>
     *  <li><code>TEXTURE_TILED_X</code></li>
     *  <li><code>TEXTURE_TILED_Y</code></li>
     *  </ul>
     */
    public void setTextureMode(int textureMode) {
        this.textureMode = textureMode;
        
        // Re-apply the texture since mode has changed.
        if(texture != null) {
            setTexture(texture, textureX, textureY, texturePixelWidth, texturePixelHeight);
        }
    }
    
    /**
     * Set the transparency of the rectangle.
     * @param transparency  the transparency of the rectangle (0-1f).
     */    
    public void setTransparency(float transparency) {
        ColorRGBA defaultColor = getDefaultColor();
        defaultColor.a = transparency;
        updateRenderState();
    }

}
